/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jpavlich.stemLife;

import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.GhostControl;
import com.jme3.math.Vector3f;
import edu.wlu.cs.levy.CG.KeySizeException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author As You Wish
 */
public class Cell extends LivingAgent implements PhysicsCollisionListener {

    //////////////////// BEGIN GENES //////////////////////
    /**
     * This is to decide how alike are two or more cells.
     * The closer the values of Cell#groupingFeature, the more probabilities
     * that two cells will group.
     */
    @Gene(1.0)
    double groupingFeature;
    @Gene(1.0)
    double maxForce;
    @Gene(1.0)
    double reactionTime;
    @Gene(1.0)
    @Display
    double hungerThreshold;
    @Gene(1.0)
    double reproductionEnergyThreshold;
    @Gene(1.0)
    double movingEnergyConsumption;
    @Gene(1e-4)
    @Display
    Probability reproductionProbability;
    /////////////////// END GENES /////////////////////
    /**
     * Set of cells that this cell follows (after findNearbyCell with them)
     */
    @Display
    private Set<Cell> following = new HashSet<Cell>();
    @Display
    private Set<Cell> followers = new HashSet<Cell>();
//    GhostControl detectionSphere;

    public Cell() {
        super();
    }

    @Override
    public void update(float tpf) {
        super.update(tpf);
        if (isAlive) {
            decideReproduction(tpf);
//            decideGrouping(tpf);
            currentAction.update(tpf);
        }
    }

    @Override
    public void collision(PhysicsCollisionEvent event) {
        if (event.getObjectA() instanceof GhostControl || event.getObjectB() instanceof GhostControl) {
            return;
        }
        if (event.getNodeA() != event.getNodeB()) {
            if (currentAction != interactingWithTarget) {
                LivingAgent target = null;
                if (event.getNodeA() == spatial) {
                    target = event.getNodeA().getControl(LivingAgent.class);
                } else if (event.getNodeB() == spatial) {
                    target = event.getNodeB().getControl(LivingAgent.class);
                }
                if (target != null) {
                    interactingWithTarget.target = target;
                    currentAction = interactingWithTarget;
                }
            }
        }

    }

    /////////////////// BEGIN ACTIONS //////////////////
    abstract class Action {

        abstract void update(float tpf);
    }

    class Idle extends Action {

        @Override
        void update(float tpf) {
            if (isHungry()) {
                currentAction = findingEnergySource;
                Logger.getLogger(Cell.class.getName()).log(Level.INFO, "{0}: Finding energy source", hashCode());
            } else {
                followLeader();
            }
        }
    }

    class FindingEnergySource extends Action {

        @Override
        void update(float tpf) {

            // Energy sources can be Food or Cells
            // The cell first tries to follow a nearby leader cell
            // to get energy from
            findEnergySource();
        }
    };

    class MovingToTarget extends Action {

        LivingAgent target;
        private Set<Cell> movedCells = new HashSet<Cell>();

        @Override
        void update(float tpf) {
            moveToTarget(target);
            movedCells.clear();
            moveFollowers(Cell.this, movedCells);

        }

        private void moveFollowers(Cell c, Set<Cell> movedCells) {
            movedCells.add(c);
            for (Cell f : followers) {
                if (!movedCells.contains(f)) {
                    f.moveToTarget(target);
                    moveFollowers(f, movedCells);
                }
            }
        }
    }
    // FIXME For some reason it is not pairing with other cells
    class InteractingWithTarget extends Action {

        LivingAgent target;

        @Override
        void update(float tpf) {
            if (isHungry()) {
                absorbEnergyFrom(target);
                currentAction = idle;
            } else {
                currentAction = idle;
            }
            if (target instanceof Cell) {
                pairWith((Cell) target);
            }

        }
    }

    class FindNearbyCell extends Action {

        @Override
        void update(float tpf) {

            Cell target = StemLife.INSTANCE.getRandomCell();
            movingToTarget.target = target;
            currentAction = movingToTarget;


//            if (!detectionSphere.isEnabled()) {
//                detectionSphere.setEnabled(true);
//                return;
//            }
//            if (detectionSphere.isEnabled() && detectionSphere.getOverlappingCount() > 0) {
//                List<PhysicsCollisionObject> objs = detectionSphere.getOverlappingObjects();
//                for (PhysicsCollisionObject obj : objs) {
//                    if (obj instanceof Cell) {
//                        movingToTarget.target = (LivingAgent) obj;
//                        currentAction = movingToTarget;
//                        break;
//                    }
//                }
//                detectionSphere.setEnabled(false);
//            }
        }
    }
    FindingEnergySource findingEnergySource = new FindingEnergySource();
    MovingToTarget movingToTarget = new MovingToTarget();
    Idle idle = new Idle();
    InteractingWithTarget interactingWithTarget = new InteractingWithTarget();
    FindNearbyCell findNearbyCell = new FindNearbyCell();
    Action currentAction = idle;

    //////////////////// END ACTIONS ///////////////////
    public void absorbEnergyFrom(LivingAgent livingAgent) {
        if (energy + livingAgent.energy > maxEnergy) {
            livingAgent.energy -= maxEnergy - energy;
            energy = maxEnergy;
        } else {
            energy += livingAgent.energy;
            livingAgent.energy = 0;
            livingAgent.die();
        }
        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "E: {0}", energy);

    }

    public void decideReproduction(float tpf) {
        if (reproductionProbability.satisfies()) {
            reproduce();
            reproductionProbability.reset();
        } else {
            if (energy > maxEnergy * reproductionEnergyThreshold) {
                reproductionProbability.increase(tpf);
            }
        }
    }

    public Cell reproduce() {
        Vector3f pos = getPhysicsLocation();
//        double mass = getMass();
        double r = ((SphereCollisionShape) getCollisionShape()).getRadius();
        double phi = (Math.random() * 2 * Math.PI);
        double theta = (Math.random() * 2 * Math.PI);
        double sinPhi = Math.sin(phi);
        double sinTheta = Math.sin(theta);
        double cosPhi = Math.cos(phi);
        double cosTheta = Math.cos(theta);

        pos.addLocal(new Vector3f((float) (r * sinTheta * cosPhi), (float) (r * sinTheta * sinPhi), (float) (r * cosTheta)));

        Cell c = StemLife.INSTANCE.createCell(pos, (float) (mass * (1 + Math.random() * 0.1 - 0.05)));
        c.inheritDna(this);
        c.energy = energy;
        // Distributes total energy among both cells
        energy *= reproductionEnergyThreshold;
        c.energy *= (1 - reproductionEnergyThreshold);
        currentAction = idle;
        return c;
    }

    /**
     * Form a group between this cell and another one. Two cells are more likely to pair
     * with each other only if their attributes Cell#groupingFeature are more
     * similar.
     * The cell with less max energy will follow the cell with more max energy
     * @see Cell#following
     * @param other The oher cell to pair with
     */
    private void pairWith(Cell other) {
        if (this != other) {
            if (Math.random() < 1 - Math.abs(groupingFeature - other.groupingFeature)) {
                if (maxEnergy > other.maxEnergy) {
                    other.follow(this);
                } else {
                    follow(other);
                }
            }
        }
    }

    public void follow(Cell leader) {
        following.add(leader);
        leader.followers.add(this);
    }

    private boolean isHungry() {
        return energy < maxEnergy * hungerThreshold;
    }

    /**
     * Follows a leader (a cell belonging to Cell#following
     * @return true if this cell follows at least another cell, false otherwise
     */
    private boolean followLeader() {
        // FIXME It must choose the closest cell belonging to its group
        if (following.size() > 0) {
            Cell leader = following.iterator().next();
            movingToTarget.target = leader;
            currentAction = movingToTarget;
            return true;
        } else {
            return false;
        }
    }

    private void findEnergySource() {
        Vector3f pos = getPhysicsLocation();
        if (StemLife.INSTANCE.foods.size() > 0) {
            try {
                // If food is found, moves there
                Food f = (Food) StemLife.INSTANCE.foods.nearest(new double[]{pos.x, pos.y, pos.z});
                movingToTarget.target = f;
                currentAction = movingToTarget;
                Logger.getLogger(Cell.class.getName()).log(Level.INFO, "{0}: Going for food", hashCode());
            } catch (KeySizeException ex) {
                Logger.getLogger(Cell.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            // If no food is found, tries to find a leader cell, a follower cell, or any cell nearby
            if (followLeader()) {
            } else if (followers.size() > 0) {
                movingToTarget.target = followers.iterator().next();
                currentAction = movingToTarget;
            } else {
                currentAction = findNearbyCell;
            }

        }
    }

    private void moveToTarget(LivingAgent target) {
        Vector3f goalDir = target.getPhysicsLocation().subtract(getPhysicsLocation());
        Vector3f vel = getLinearVelocity();
        Vector3f force = goalDir.add(goalDir.subtract(vel)).divide(5.0f);
        applyCentralForce(force);
        double de = movingEnergyConsumption;
        energy -= de * force.length();
    }

    @Override
    public void die() {
        super.die();
        StemLife.INSTANCE.destroyAgent(this);

        following.clear();

        // Dettach followers
        Iterator<Cell> it = followers.iterator();
        while (it.hasNext()) {
            it.next().following.remove(this);
        }

        StemLife.INSTANCE.createFood(getPhysicsLocation());
        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "{0}: DIE", hashCode());

    }
//    public void setDetectionSphere(GhostControl detectionSphere) {
//        this.detectionSphere = detectionSphere;
//        spatial.addControl(detectionSphere);
//
//    }
}
